%     FASTENER DOCTOR - программа для лечения и конвертации крепежа
%     Copyright (C) 2018  Михаил Козлов
%     Сайт проекта: <http://www.fastenerdoctor.ru/>
%     Адрес электронной почты для связи с разработчиком:
%     <doctor.fastener@gmail.com>
% 
%     Эта программа является свободным программным обеспечением: Вы можете
%     распространять ее и/или изменять, соблюдая условия Универсальной общественной
%     лицензии GNU, опубликованной Фондом свободного программного обеспечения,
%     либо редакции 3, либо (на Ваше усмотрение) любой редакции, выпущенной позже.
% 
%     Эта программа распространяется в надежде на то, что она окажется полезной,
%     но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, включая даже подразумеваемую гарантию ТОВАРНОЙ ПРИГОДНОСТИ,
%     либо ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Ознакомьтесь с Универсальной общественной
%     лицензией GNU для получения более подробной информации.
% 
%     Вы должны были получить копию Универсальной общественной лицензии GNU
%     вместе с этой программой. Если Вы ее не получили, то перейдите по адресу:
%     <http://www.gnu.org/licenses/>.
% 
%     This program is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
% 
%     This program is distributed in the hope that it will be useful,
%     but WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%     GNU General Public License for more details.
% 
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see <https://www.gnu.org/licenses/>.

function Test_Result = Hole_2D_Test(Center_Node, Connected_Nodes, Excluded_Elements)

% Test_Result = 1    - присоединенная топология является отверстием
% Test_Result = 0    - присоединенная топология не является отверстием

global DB_NODES;
global DB_ELEMENTS;

global AT_NODES;
global AT_ELEMENTS;

Test_Result = 0;

% --- Формирование массива угловых узлов элементов на предполагаемой кромке отверстия ---

Nodes_array = zeros(1000,1);

counter = 0;

for i=1:size(Connected_Nodes,1)
    
    Elm_list = Get_Elements_From_Nodes(Connected_Nodes(i), Excluded_Elements);
    
    Corner_Nodes = Shell_Corners_From_Node(Elm_list(1), Connected_Nodes(i));
    
    for j=1:size(Corner_Nodes,1)
        
        counter = counter + 1;
        Nodes_array(counter) = Corner_Nodes(j);
        
    end
    
end

Nodes_array = Nodes_array(1:counter);
Nodes_array = unique(Nodes_array);

% --- Поиск замкнутых цепочек свободных ребер shell элементов для каждого узла из массива ---

Edge_chains = cell(size(Nodes_array,1),1);

for i=1:size(Nodes_array,1)
    
    % Выделение памяти
    Edge_chains{i} = zeros(1000,1);
    
    % Запись первого узла
    Edge_chains{i}(1) = Nodes_array(i);
    Chain_nodes_counter = 1;
    
    % Текущий узел
    Current_node = Nodes_array(i);
    
    while(1)
        
        % Получение списка элементов, ассоциированных с текущим узлом
        Elm_list = Get_Elements_From_Nodes(Current_node, Excluded_Elements);

        % Проверка на пустой список
        if (isempty(Elm_list))
            return;
        end

        % Проверка на shell элементы
        for j=1:size(Elm_list,1)
            if (~(strcmp(DB_ELEMENTS{AT_ELEMENTS(Elm_list(j))}.type, 'Quad') || strcmp(DB_ELEMENTS{AT_ELEMENTS(Elm_list(j))}.type, 'Tria')))
                return;
            end
        end

        % Получение списка узлов, формирующих ребра ассоциированных элементов в паре с текущим узлом
        Edge_nodes = Shell_Edges_From_Node(Elm_list, Current_node);

        % Выделение памяти
        Free_edge_nodes = zeros(1000,1);
        Free_edge_counter = 0;

        % Получение списка узлов, формирующих свободные ребра ассоциированных элементов в паре с текущим узлом
        for j=1:size(Edge_nodes,1)

            if (numel(intersect(Get_Elements_From_Nodes(Current_node, Excluded_Elements), Get_Elements_From_Nodes(Edge_nodes(j), Excluded_Elements))) == 1)

                Free_edge_counter = Free_edge_counter + 1;
                Free_edge_nodes(Free_edge_counter) = Edge_nodes(j);

            end

        end

        % Должно быть два узла, формирующих свободные ребра ассоциированных элементов в паре с текущим узлом
        if (Free_edge_counter ~= 2)
            return;
        end

        % Финализация массива Free_edge_nodes
        Free_edge_nodes = Free_edge_nodes(1:Free_edge_counter);
        
        % Если это первое расширение цепочки
        if (Chain_nodes_counter == 1)
            
            % Пополнение цепочки первым найденным узлом и переход к следующей итерации
            Chain_nodes_counter = Chain_nodes_counter + 1;
            Edge_chains{i}(Chain_nodes_counter) = Free_edge_nodes(1);
            Current_node = Free_edge_nodes(1);
            
            continue;
            
        else % Если это не первое расширение цепочки
            
            % Поиск нового узла
            if (Edge_chains{i}(Chain_nodes_counter-1) == Free_edge_nodes(1))
                New_node = Free_edge_nodes(2);
            else
                New_node = Free_edge_nodes(1);
            end
            
            % Если новый узел является первоначальным, то завершаем формирование цепочки
            if (New_node == Edge_chains{i}(1))
                break;
            end
            
            % Пополнение цепочки новым узлом и переход к следующей итерации
            
            Chain_nodes_counter = Chain_nodes_counter + 1;
            Edge_chains{i}(Chain_nodes_counter) = New_node;
            Current_node = New_node;
            
            continue;
            
        end
        
    end
    
    % Финализация цепочки
    Edge_chains{i} = Edge_chains{i}(1:Chain_nodes_counter);
    
end

% --- Сравнение цепочек ---

for i=2:size(Edge_chains,1)
    
    if (~Compare_Chains(Edge_chains{1}, Edge_chains{i}))
        return;
    end
    
end

% --- Исключение случая "отверстия наизнанку" ---

% Формирование списка пар узлов
Node_pairs = zeros(size(Edge_chains{1},1),2);

for i=1:size(Edge_chains{1},1)-1
    
    Node_pairs(i,1) = Edge_chains{1}(i);
    Node_pairs(i,2) = Edge_chains{1}(i+1);
    
end

Node_pairs(size(Edge_chains{1},1),1) = Edge_chains{1}(size(Edge_chains{1},1));
Node_pairs(size(Edge_chains{1},1),2) = Edge_chains{1}(1);

% Проверка положений центроидов элементов
for i=1:size(Edge_chains{1},1)
    
    Element_ID = intersect(Get_Elements_From_Nodes(Node_pairs(i,1), Excluded_Elements), Get_Elements_From_Nodes(Node_pairs(i,2), Excluded_Elements));
    
    if (numel(Element_ID) ~= 1)
        return;
    end
    
    Centroid = Get_Shell_Centroid(Element_ID);
    
    Projected_Center_Node = Project_Point_Plane_3P(Center_Node, Node_pairs(i,1), Node_pairs(i,2), Centroid);
    
    Line_location_1 = Projected_Center_Node;
    Line_location_2 = Projected_Center_Node + DB_NODES{AT_NODES(Node_pairs(i,2))}.Location - DB_NODES{AT_NODES(Node_pairs(i,1))}.Location;
    
    if (Get_Point_Line_Distance(Centroid, Line_location_1, Line_location_2) <= Get_Point_Line_Distance(Node_pairs(i,1), Line_location_1, Line_location_2))
        return;
    end
    
end

% --- Положительный результат теста ---

Test_Result = 1;
